package docbookarchivemanager.utils;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import javax.xml.xpath.XPathFactoryConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 *
 * @author Jakub Hrachovec
 */
public class DomManager {

    public static final File XSD_FILE = new File("./xsd/docbook.xsd");

    public static Document readDom(File input, boolean XIncludeAware) {
        try {
            DocumentBuilderFactory domFactory
                    = DocumentBuilderFactory.newInstance("com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl", null);
            domFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
            domFactory.setNamespaceAware(true);
            domFactory.setXIncludeAware(XIncludeAware);
            DocumentBuilder builder = domFactory.newDocumentBuilder();

            Document doc = builder.parse(input);

            return doc;

        } catch (SAXException | ParserConfigurationException | IOException ex) {
            System.out.println(String.format("ERROR: %s", ex.getMessage()));
        }

        return null;
    }

    public static Document readDom(File file) {
        return readDom(file, false);

    }

    public static Document readDom(InputStream input) {
        try {
            DocumentBuilderFactory domFactory
                    = DocumentBuilderFactory.newInstance("com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl", null);
            domFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
            domFactory.setNamespaceAware(true);
            DocumentBuilder builder = domFactory.newDocumentBuilder();

            Document doc = builder.parse(input);

            return doc;

        } catch (SAXException | ParserConfigurationException | IOException ex) {
            System.out.println(String.format("ERROR: %s", ex.getMessage()));
        }
        return null;
    }

    /**
     * Writes given Dom to given path.
     *
     * @param doc
     * @param path
     */
    public static void writeDom(Document doc, String path) {

        TransformerFactory transformerFactory = TransformerFactory.newInstance("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl", null);
        Transformer transformer;
        try {
            transformer = transformerFactory.newTransformer();
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");

            DOMSource source = new DOMSource(doc);
            File file = new File(path);
            StreamResult result = new StreamResult(file);

            transformer.transform(source, result);
        } catch (TransformerException ex) {
            System.out.println(ex.getMessage());
        }
    }

    /**
     *
     * @param nl
     * @return List of elements without text nodes.
     */
    public static ArrayList<Node> getOnlyElements(NodeList nl) {
        ArrayList<Node> temp = new ArrayList<>();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node.getNodeType() == 1) {
                temp.add(node);
            }
        }
        return temp;
    }

    /**
     *
     * @param doc
     * @param express
     * @return nodes that succeeded
     */
    public static NodeList evaluateXpath(Document doc, String express) {
        try {
            XPathFactory xpathFactory = XPathFactory.newInstance(XPathConstants.DOM_OBJECT_MODEL, "com.sun.org.apache.xpath.internal.jaxp.XPathFactoryImpl", null);
            XPath xpath = xpathFactory.newXPath();

            xpath.setNamespaceContext(new NamespaceContextImpl());

            XPathExpression expr = xpath.compile(express);
            Object result = expr.evaluate(doc, XPathConstants.NODESET);
            NodeList nodes = (NodeList) result;
            return nodes;
        } catch (XPathExpressionException | XPathFactoryConfigurationException ex) {
            System.out.println(ex.getMessage());
        }
        return null;
    }

    public static Boolean validateXmlFile(File xsd, File xml) throws SAXException, IOException {
        Source schemaFile = new StreamSource(xsd);
        Source xmlFile = new StreamSource(xml);
        SchemaFactory schemaFactory = SchemaFactory
                .newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        Schema schema = schemaFactory.newSchema(schemaFile);
        Validator validator = schema.newValidator();
        try {
            validator.validate(xmlFile);
            return true;
        } catch (SAXException e) {
            System.out.println(xml.getAbsolutePath() + " is NOT a valid DocBook 5.0 document.");
            throw new SAXException(e);
        }
    }
}
